# coding:utf-8
import numpy as np
import pandas as pd
# 柯里化一个print
printn = lambda *x: print(*x, sep='\n')

# import re


def testopen():
    '''
    读写文本格式:(read_csv和read_table可能用的最多)
    read_csv:从文件、URL、文件型对象中加载带分隔符的数据。默认分隔符逗号
    read_table:从文件、URL、文件型对象中加载带分隔符的数据。默认分隔符为制表符('\t')
    read_fwf:读取定宽列格式数据，也就是说没有分隔符
    read_clipboard:读取剪贴板中的数据，可以看作read_table的剪贴板版。在将网页转换为表格时很有用
    read_excel:读取Excel的xls或xlsx文件；    read_hdf:读取pandas写的HDF5文件
    read_html:读取HTML文档中的所有表格；read_json:读取JSON字符串中的数据
    read_msgpack:读取二进制格式编码的pandas数据；read_pickle:读取Python pickle格式中存储的任意对象
    read_sas:读取存于SAS系统自定义存储格式的SAS数据集；read_stata:读取Stata文件格式的数据集
    read_sql:（使用SQLAlechemy）读取SQL查询结果为pandas的DataFrame
    '''
    # 使用read_csv,使用默认分隔符,默认首行是列名
    df = pd.read_csv('examples/ex1.csv')
    printn('read_csv例子:', df)
    # 也可以使用read_table，并指定分隔符,默认首行是列名
    df = pd.read_table('examples/ex1.csv', sep=',')
    printn('read_table指定分隔符逗号:', df)

    # 并不是所有的文件都有标题行。可以让pandas为其分配默认的列名，或自己指定
    df = pd.read_csv('examples/ex2.csv', header=None)
    printn("header为None时:", df)
    names = ['a', 'b', 'c', 'd', 'message']
    df = pd.read_csv('examples/ex2.csv', names=names)
    printn("使用names指定列名时，不需要header参数:", df)

    # 指定特定列为索引时，使用index_col参数
    df = pd.read_csv('examples/ex2.csv', names=names, index_col='message')
    printn("指定索引列:", df)

    # 将多个列做成一个层次化索引，只需传入由列编号或列名组成的列表
    parsed = pd.read_csv('examples/csv_mindex.csv', index_col=['key1', 'key2'])
    printn('两列名列表指明层次化索引:', parsed)

    # 有些情况，表格是不用固定的分隔符去分割字符，比如混用长度不等的空格
    # 虽然可以手动进行规整，但数据大就太麻烦了。可以传入一个正则表达式作为分隔符
    # 本例中由于列名数量⽐数据⾏的数量列数少，所以read_table推断第⼀列应该是DataFrame的索引。
    # result=pd.read_csv('examples/ex3.csv',sep='\s+')
    result = pd.read_table('examples/ex3.csv', sep='\\s+')
    printn('正则表达式分隔符:', result)

    # 跳过文件的指定行,多行应该传入list
    printn('跳过多行读取:', pd.read_csv('examples/ex4.csv', skiprows=[0, 2, 3]))

    # 缺失值处理。缺失数据经常是要么没有(空字符串),要么用某个标记值表示。
    # 默认，pandas会用一组经常出现的标记值进行识别，如NA或NULL
    result = pd.read_csv('examples/ex5.csv')
    printn('读取的内容:', result)
    printn('空值判断:', pd.isnull(result))
    # na_values可以⽤⼀个列表或集合的字符串额外表示缺失值。na_values表示把列表里的东西在DF里变成NAN
    result = pd.read_csv('examples/ex5.csv', na_values=['NULL'])
    printn('缺失值填充NA:', result)
    # 字典的各列可以使⽤不同的NA标记值,
    sentinels = {'message': ['foo', 'NA'], 'something': ['two']}
    result = pd.read_csv('examples/ex5.csv', na_values=sentinels)
    printn('不同列指定内容填充NA:', result)
    '''
    read_csv和read_table的参数：
    path--表示文件系统位置、URL、文件型对象的字符串； sep或delimiter--间隔符，可以是字符串或正则表达式；
    header--用做列名的行号。默认为0(第一行)，如果没有header行应该设置为None
    index_col--用做行索引的列编号或列名。可以是单个名称/数字，也可是其组成的列表。
    names--用于结果的列名列表
    skiprows--需要忽略的行数(从文件开头算起)，或需要跳过的行号列表
    na_values--一组用于替换NA的值，即该字符串读取成NAN
    comment--用于将注释信息从行尾拆分出去的字符(一个或多个)
    parse_dates--尝试将数据解析成日期，默认为False。如果为True，则尝试解析所有列。此外可以指定需要解析的一组列号或列名。
                 如果列表的元素为列表或元组，就会将多个列组合到一起再进行日期解析(例如，日期/时间分别位于两个列中)
    keep_date_col--如果连接多列解析日期，则保持参与连接的列。默认为False
    converters--由列号/列名跟函数之间的映射关系组成的字典。例如,{'foo':f}会对foo列的所有值应用函数f
    dayfirst--当解析有歧义的日期时，将其看做国际格式(例如，7/6/2012--—>June 7,2012)。默认False
    date_parse--用于解析日期的函数
    nrows--需要读取的行数（从文件开始处算起)
    interator--返回一个TextParse以便逐块读取文件
    chunksize--文件块的大小(用于迭代)
    skip_footer-需要忽略的行数(从文件末尾处算起)
    verbose--打印各种解析器输出信息，比如"非数值列中缺失值的数量"等
    encoding--用于unicode的文本编码格式。例如“utf-8”表示用UTF-8编码的文本
    squeeze--如果数据经解析后仅含一列，则返回Series
    thousands--千分位分隔符，如","或'.'
    '''
# testopen()


# 逐块读取文本文件
# 只想读取文件的一小部分或逐块对文件进行迭代。
def testblock():
    # 在看大文件之前，先设置pandas显示地更紧些：
    pd.options.display.max_rows = 10
    '''
    # 全部读取
    result=pd.read_csv('examples/ex6.csv')
    print(result.head())
    '''
    # 避免读取整个文件，通过nrows指定
    result = pd.read_csv('examples/ex6.csv', nrows=5)
    printn('前5行:', result)
    # 要逐块读取文件，可以指定chunksize（行数）
    chunker = pd.read_csv(
        'examples/ex6.csv', chunksize=1000)  # 可以迭代出来，是一个TextParser对象
    # print(chunker)
    # 将值计数聚合到'key'列中
    tot = pd.Series([], dtype='u8')  # 新建一个空Series,u8就是float64类型
    for piece in chunker:
        tot = tot.add(piece['key'].value_counts(), fill_value=0)
    tot = tot.sort_values(ascending=False)
    printn('计数的前10行:', tot[:10])
# testblock()


# 将数据写出到文本格式
def testwrite():
    data = pd.read_csv('examples/ex5.csv')
    # 利用DataFrame的to_csv方法，可以将数据写到一个以逗号分割的文件中
    data.to_csv('examples/out.csv')

    # 也可使用其他分隔符，比如写到屏幕sys.stdout
    import sys
    data.to_csv(sys.stdout, sep='|')

    # 缺失值在输出结果中表示为空字符串，也可以表示成其他值
    data.to_csv(sys.stdout, na_rep='NULL')

    # 如果没有其他选项，则会写出行和列的标签，当然也可以禁用
    data.to_csv(sys.stdout, index=False, header=False)

    # 也可只写一部分的列，并指定顺序
    data.to_csv(sys.stdout, index=False, columns=['a', 'b', 'c'])

    # Series也有一个to_csv方法
    dates = pd.date_range('1/1/2019', periods=7)
    printn('日期:', dates)
    ts = pd.Series(np.arange(7), index=dates)
    ts.to_csv('examples/tseries.csv', header=False)
# testwrite()


# 处理分隔符格式
def testdot():
    # 对于任何单字符分隔文件，可以直接使用Python内置的csv模块。将任意已打开的文件或文件型对象传给csv.reader
    import csv
    f = open('examples/ex7.csv')
    reader = csv.reader(f)
    # 对这个reader进行迭代将会为每行产生一个元组(并移除了所有的引号):
    for line in reader:
        print(line)
    # 为使数据格式合乎要求，你需要对其做一些整理工作。首先读取到一个多行的列表中
    with open('examples/ex7.csv') as f:
        lines = list(csv.reader(f))
    # 将行分为标题行和数据行
    header, values = lines[0], lines[1:]
    # 然后用字典构造式和zip(*values)，后者将行转置为列，创建数据列的字典
    data_dict = {h: v for h, v in zip(header, zip(*values))}
    printn('构造的字典:', data_dict)

    # csv文件的形式很多，定义csv.Dialect的一个子类就可以定义出新格式(如专门的分隔符、字符串引用约定、行结束符等)
    class my_dialect(csv.Dialect):
        lineterminator = '\n'
        delimiter = ','
        quotechar = '"'
        quoting = csv.QUOTE_MINIMAL

    with open('examples/ex7.csv') as f:
        reader = csv.reader(f, dialect=my_dialect)
        for line in reader:
            print(line)

# 各个csv语支的参数也可以用关键字形式提供给csv.reader,无需定义子类
# reader=csv.reader(f,delimiter='|')
    '''
    可用的语支选项：delimiter--用于分隔字段的单字符字符串，默认","
    lineterminator--用于写操作的行结束符，默认'\r\n'
    quotechar--用于带有特殊字符（如分隔符）的字段的引用符号。默认'"'
    quoting--引用约定。可选值包括csv.QUOTE_ALL(引用所有字段)，csv.QUOTE_MINIMAL(只引用带有诸如分隔符之类特殊字字符的字段)、csv.QUOTE_NONNUMERIC以及csv.QUOTE_NONE(不引用)。
    skipinitialspace--忽略分隔符后面的空白符，默认False
    doublequote--如何处理字段内的引用符号。如果为True，则双写。
    escapechar--用于对分隔符进行转义的字符串(如果quoting被设置成csv.QUOTE_NONE的话)，默认禁用
    '''

    # 要手工输入分隔符文件，可以使用csv.writer。
    with open('examples/mydata.csv', 'w') as f:
        writer = csv.writer(f, dialect=my_dialect)
        writer.writerow(('one', 'two', 'three'))
        writer.writerow(('1', '2', '3'))
        writer.writerow(('4', '5', '6'))
        writer.writerow(('7', '8', '9'))
# testdot()


# JSON格式
def testjson():
    # 除其空值null和⼀些其他的细微差别（如列表末尾不允许存在多余的逗号）之外，JSON⾮常接近于有效的Python代码
    # 。对象中所有的键都必须是字符串
    obj = """
    {"name":"Wes",
    "places_lived":["United States","Spain","Germany"],
    "pet":null,
    "siblings":[{"name":"Scott","age":30,"pets":["Zeus","Zuko"]},
                {"name":"Katie","age":38,"pets":["Sixes","Stache","Cisco"]}]
    }
    """
    import json
    result = json.loads(obj)
    print(type(result))
    printn('json字符串转为字典对象:', result)
    # 将Python字典对象转换为JSON格式
    asjson = json.dumps(result)

    # 构建Dataframe：向DataFrame构造器传⼊⼀个字典的列表（就是原先的JSON对象），并选取数据字段的⼦集
    siblings = pd.DataFrame(result['siblings'], columns=['name', 'age'])
    printn('JSON对象构造的DataFrame:', siblings)

    # pandas.read_json() 默认选项假设JSON数组中的每个对象是表格中的⼀⾏：
    data = pd.read_json('examples/example.json')
    printn('读入json:', data)

    # 将数据从pandas输出到JSON，可以使⽤to_json⽅法
    printn('输出到json:', data.to_json())
    print(data.to_json(orient='records'))  # records形成column:value键值对的字典组成的列表
# testjson()


# XML和HTML
# pip install lxml
# pandas有⼀个内置的功能，read_html，它可以使⽤lxml和Beautiful    Soup⾃动将HTML⽂件中的表格解析为DataFrame对象。
# pandas.read_html有⼀些选项，默认条件下，它会搜索、尝试解析标签内的的表格数据。结果是⼀个列表的DataFrame对象
# 从美国联邦存款保险公司下载了⼀个HTML⽂件（pandas⽂档中也使⽤过），它记录了银⾏倒闭的情况
def testhtml():
    tables = pd.read_html('examples/fdic_failed_bank_list.html')
    print(len(tables))
    failures = tables[0]
    printn("倒闭银行信息:", failures.head())

    # 做一些数据清洗和分析，比如计算按年份计算倒闭的银⾏数
    close_timestamps = pd.to_datetime(failures['Closing Date'])  # 转换成时间序列
    printn('倒闭时间:', close_timestamps)
    printn('某年倒闭的银行数:', close_timestamps.dt.year.value_counts())
# testhtml()


# XML（Extensible    Markup    Language）是另⼀种常⻅的⽀持分层、嵌套数据以及元数据的结构化数据格式
# 。XML和HTML的结构很相似，但XML更为通⽤。这⾥，⽤⼀个例⼦演示如何利⽤lxml从XML格式解析数据。
# 利用lxml.objectify解析大都会运输署的公交列车数据,然后通过getroot得到该XML⽂件的根节点的引⽤：
def testxml():
    from lxml import objectify
    path = 'datasets/mta_perf/Performance_MNR.xml'
    parsed = objectify.parse(open(path))
    root = parsed.getroot()
    # INDICATOR是给文件里的一个子节点
    # root.INDICATOR返回⼀个⽤于产⽣各个XML元素的⽣成器。对于每条记录，我们可以⽤标记名（如YTD_ACTUAL）和数据值填充⼀个字典（排除⼏个标记）：
    data = []
    skip_fields = [
        'PARENT_SEQ', 'INDICATOR_SEQ', 'DESIRED_CHANGE', 'DECIMAL_PLACES'
    ]
    for elt in root.INDICATOR:
        el_data = {}
        for child in elt.getchildren():
            if child.tag in skip_fields:
                continue
            el_data[child.tag] = child.pyval
        data.append(el_data)
    # 最后，将这组字典转换为⼀个DataFrame：
    perf = pd.DataFrame(data)
    printn('公交列车数据:', perf.head())

    # XML数据可以⽐本例复杂得多。每个标记都可以有元数据。看看下⾯这个HTML的链接标签（它也算是⼀段有效的XML）：
    from io import StringIO
    tag = '<a href="http://www.google.com">Google</a>'
    root = objectify.parse(StringIO(tag)).getroot()
    # 现在就可以访问标签或链接⽂本中的任何字段了（如href）
    printn('根元素:', root, '根链接:', root.get('href'), '根文本:', root.text)
# testxml()


# 二进制格式数据
# 实现数据的⾼效⼆进制格式存储最简单的办法之⼀是使⽤Python内置的pickle序列化。
# pandas对象都有⼀个⽤于将数据以pickle格式保存到磁盘上的to_pickle⽅法：
# 尽量不要使用pickle格式存储，不能保证pickle文件会被后续版本读取出来
def testPickle():
    frame = pd.read_csv('examples/ex1.csv')
    print(frame)
    frame.to_pickle('examples/frame_pickle')

    # 可以通过pickle直接读取被pickle化的数据，或是使⽤更为⽅便的pandas.read_pickle：
    picklefile = pd.read_pickle('examples/frame_pickle')
    print(picklefile)
# testPickle()


# 使用HDF5格式，由于许多数据分析问题都是IO密集型（⽽不是CPU密集型），利⽤HDF5这样的⼯具能显著提升应⽤程序的效率。
# HDF5不是数据库。它最适合⽤作“⼀次写多次读”的数据集。虽然数据可以在任何时候被添加到⽂件中，但如果同时发⽣多个写操作，⽂件就可能会被破坏。
'''
HDF5中的HDF指的是层次型数据格式（hierarchical    data
format）。每个HDF5⽂件都含有⼀个⽂件系统式的节点结构，它使你能够存储多个数据集并⽀持元数
据。与其他简单格式相⽐，HDF5⽀持多种压缩器的即时压缩，还能更⾼效地存储重复模式数据。对于
那些⾮常⼤的⽆法直接放⼊内存的数据集，HDF5就是不错的选择，因为它可以⾼效地分块读写。
'''


# 需要tables支持，pip install tables
def testhdf5():
    frame = pd.DataFrame({'a': np.random.randn(100)})
    printn('DataFrame:', frame)
    store = pd.HDFStore('mydata.h5')
    # 查看store类型
    printn('hdf5文件类型', store)
    # 利用键值对将不同的数据存入store对象中，这里为了代码简洁使用了元组赋值法
    # dataframe存储
    store['obj1'] = frame
    # HDF5⽂件中的对象可以通过与字典⼀样的API进⾏获取
    printn('obj1对象:', store['obj1'])
    # series存储
    store['obj1_col'] = frame['a']
    printn('修改过的store', store['obj1_col'])

    # HDFStore⽀持两种存储模式，'fixed'和'table'。后者通常会更慢，但是⽀持使⽤特殊语法进⾏查询操作：
    '''
    利用store的put()方法将数据存入store对象中，其主要参数如下：
　　key：指定h5文件中待写入数据的key
　　value：指定与key对应的待写入的数据
　　format：字符型输入，用于指定写出的模式，'fixed'对应的模式速度快，但是不支持追加也不支持检索；'table'对应的模式以表格的模式写出，速度稍慢，但是支持直接通过store对象进行追加和表格查询操作
    '''
    store.put('obj2', frame, format='table')
    se = store.select('obj2', where=['index>=10 and index<=15'])
    printn('选择索引:', se)
    # 关闭句柄
    store.close()

    # 新建一个hdf5，并指定key，以table格式存储数据
    frame.to_hdf('mydata2.h5', 'obj3', format='table')
    sr = pd.read_hdf('mydata2.h5', 'obj3', where=['index<5'])
    printn('读取key-obj3的数据:', sr)
# testhdf5()


# excel: 读需要xlrd支持，写需要openpyxl支持，pip install xlrd openpyxl
def testexcel():
    # 使⽤ExcelFile，通过传递xls或xlsx路径创建⼀个实例
    xlsx = pd.ExcelFile('examples/ex1.xlsx')
    # 读取
    efile = pd.read_excel(xlsx, 'Sheet1')
    printn('读取的excel:', efile)

    # 读取⼀个⽂件中的多个表单，创建ExcelFile会更快，但你也可以将⽂件名传递到read_excel
    frame = pd.read_excel('examples/ex1.xlsx', 'Sheet1')
    printn('传递文件名读取:', frame)
    '''
    使用ExcelWriter，
    # 写⼊为Excel格式，⾸先创建⼀个ExcelWriter，然后使⽤pandas对象的to_excel⽅法将数据写⼊到其中
    writer=pd.ExcelWriter('examples/ex2.xlsx')
    frame.to_excel(writer,'Sheet1')
    writer.save()
    '''
    # 也可以不使⽤ExcelWriter，⽽是传递⽂件的路径到to_excel：
    frame.to_excel('examples/ex2.xlsx')
# testexcel()


# Web APIs交互---requests库
# 搜索最新的30个GitHub上的pandas主题，我们可以发⼀个HTTP    GET请求，使⽤requests扩展
def testWebAPIs():
    import requests
    url = 'https://api.github.com/repos/pandas-dev/pandas/issues'
    resp = requests.get(url)
    printn('响应对象:', resp)
    # 响应对象的json⽅法会返回⼀个包含被解析过的JSON字典，加载到⼀个Python对象中：
    data = resp.json()
    printn('字典索引:', data[0]['title'])
    # data中的每个元素都是⼀个包含所有GitHub主题⻚数据（不包含评论）的字典。我们可以直接传递数据到DataFrame，并提取感兴趣的字段：
    issues = pd.DataFrame(data, columns=['number', 'title', 'labels', 'state'])
    printn('提取出来的DataFrame:', issues)
# testWebAPIs()


# 数据库交互，以sqlite3为例
def testSQL():
    import sqlite3
    query = """
    CREATE    TABLE    test
    (a    VARCHAR(20),    b    VARCHAR(20),
    c    REAL,                                d    INTEGER
    );"""
    con = sqlite3.connect('mydata.sqlite')
    try:
        qu = con.execute(query)
        printn('执行结果:', qu)
        con.commit()
    except Exception:
        pass

# 插入几行数据
    data = [
        ('Atlanta', 'Georgia', 1.25, 6), ('Tallahassee', 'Florida', 2.6, 3),
        ('Sacramento', 'California', 1.7, 5)
    ]
    stmt = "INSERT INTO test VALUES(?,?,?,?)"
    # executemany插入多条语句，第一个参数是模板，用？替代要插入的数据
    qu = con.executemany(stmt, data)
    printn('执行结果:', qu)
    con.commit()  # 必须提交，否则不会写入数据库文件，只在缓存里
    # 读取数据
    cursor = con.execute('select * from test')
    rows = cursor.fetchall()
    printn('所有查询结果:', rows)

    # 你可以将这个元组列表传给DataFrame构造器，但还需要列名（位于光标的description属性中）
    printn('光标存储的列名:', cursor.description)
    frame = pd.DataFrame(rows, columns=[x[0] for x in cursor.description])
    printn('生成DataFrame:', frame)
    cursor.close()

    # 使用SQLAlchemy更方便
    import sqlalchemy as sqla
    db = sqla.create_engine('sqlite:///mydata.sqlite')
    printn('数据库引擎:', db)
    frame2 = pd.read_sql('select * from test', db)
    printn('read_sql直接从引擎搜索结果存入DataFrame:', frame2)


testSQL()
